package org.az.netty_study.p05_netty组件之EventLoop;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

/**
 * 这节对EventLoop的io相关操作进行详细演示
 */
@Slf4j
public class _2_EventLoop的io任务 {
    public static void main(String[] args) {
        //使用DefaultEventLoop来处理耗时的任务
        EventLoopGroup group = new DefaultEventLoop();
        ServerBootstrap server = new ServerBootstrap();
        /**
         * 我们知道，负责处理accept事件的EventLoop叫boss，负责处理读写事件的EventLoop叫worker
         * 前面的演示中，只使用一个NioEventLoopGroup，那么这个group中有的EventLoop角色是boss(只有一个)，有的是worker
         * 也可以给这两个角色单位设置NioEventLoopGroup
         * server.group(boss, worker)，虽然给boss设置了一个NioEventLoop的组，但是实际只会使用里面的一个NioEventLoop来处理accept事件，因为ServerSocketChannel只有一个
         */
        server.group(new NioEventLoopGroup(1), new NioEventLoopGroup());
        //这个没什么好说的
        server.channel(NioServerSocketChannel.class);
        server.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                /**
                 * 从channel里读到数据后，会依次调用这里添加的handler
                 * 像流水线一样依次处理数据,并将上一步处理后的数据作为下一步的参数
                 * 之前演示的所有handler都是执行在worker里的，如果handler太过耗时，就影响netty的吞吐量
                 * 为了解决这个问题，可以将耗时的handler交给其他线程处理
                 * addLast(name, ChannelInboundHandlerAdapter)，表示给添加的handler起名字
                 * addLast(EventLoopGroup, name, ChannelInboundHandlerAdapter)，表示给添加的handler起名字，并让EventLoopGroup来执行这个handler
                 */
                ch.pipeline().addLast("handler1", new ChannelInboundHandlerAdapter(){
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        //此时msg是直接从channel里读到的，是最原始的ByteBuf
                        log.info(((ByteBuf)msg).toString(Charset.defaultCharset()));
                        //调用ctx.fireChannelRead(msg)让消息传给下一个handler，否则调用链会中断
                        ctx.fireChannelRead(msg);
                    }
                }).addLast(group, "handler2", new ChannelInboundHandlerAdapter(){
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        //这里处理handler的worker就是我们自定义的DefaultEventLoop
                        Thread.sleep(2000); //模拟耗时
                        log.info(((ByteBuf)msg).toString(Charset.defaultCharset()));
                        //这是最后一个handler，不用向下传了
                    }
                });
            }
        });

        server.bind(9001);

        /**
         * 上面演示了一个流水线的不同工序(handler)，由不同的EventLoopGroup来处理
         * 那么netty是如何换人的呢?
         * io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
         * 这个方法的下列代码就是换人的关键代码
         *
         *  EventExecutor executor = next.executor(); //next表示下一个next，next.executor()返回下一个EventLoop
         *  if (executor.inEventLoop()) {   //下一个EventLoop的线程，和当前handler的线程是不是同一个线程
         *      next.invokeChannelRead(m);  //是同一个线程，不换人直接执行handler
         *  } else {    //不是
         *      executor.execute(new Runnable() {   //不是同一个线程，则将下一步任务交给指定的下一个EventLoop执行
         *          @Override
         *          public void run() {
         *              next.invokeChannelRead(m);  //这里执行这个代码的，就是其他线程了，换人了
         *          }
         *      });
         *      //当前线程在这里中断了，如果当前线程是worker，则该worker可以处理其他的channel读写事件了
         *  }
         */

    }
}
